﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="CrsSparseMatrix{T}.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra
{
    #region Usings

    using System;

    using ExulLibrary.Exceptions;

    #endregion

    /// <summary>
    ///     Represents a sparse matrix with the compressed row-major storage (CRS-format).
    /// </summary>
    /// <typeparam name="T">
    ///     The type of values of matrix elements.
    /// </typeparam>
    public sealed class CrsSparseMatrix<T> : Matrix<T>
    {
        #region Fields

        #region private

        /// <summary>
        ///     The registration index that uniquely identify matrix type.
        /// </summary>
        private static readonly int RegistrationIndex = Global.GetMatrixRegistrationIndex(typeof(CrsSparseMatrix<object>));

        #endregion

        #endregion

        #region Constructors and Destructors

        #region public

        /// <summary>
        ///     Initializes a new instance of the <see cref="CrsSparseMatrix{T}"/> class.
        /// </summary>
        /// <param name="rowsCount">
        ///     The number of rows in the new <see cref="CrsSparseMatrix{T}"/>.
        /// </param>
        /// <param name="columnsCount">
        ///     The number of columns of the new <see cref="CrsSparseMatrix{T}"/>.
        /// </param>
        /// <param name="rowsBegins">
        ///     The collection of indexes of rows begins.
        /// </param>
        /// <param name="columnsIndexes">
        ///     The collection of zero-based indexes of columns of nonzero elements.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="rowsBegins"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NegativeOrZeroSizeException">
        ///     <paramref name="rowsCount"/> is negative or equal to zero.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnsCount"/> is negative or equal to zero.
        ///     </para>
        /// </exception>
        /// <exception cref="NoEnoughElementsException">
        ///     <paramref name="rowsBegins"/> has count of elements less than <paramref name="rowsCount"/> + 1.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnsIndexes"/> has count of elements less than
        ///         <paramref name="rowsBegins"/>[<paramref name="rowsCount"/>] - <paramref name="rowsBegins"/>[0].
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The elements of <paramref name="rowsBegins"/> should be nonnegative and
        ///     each element of <paramref name="rowsBegins"/> should be equal to or greater than it's previous element,
        ///     but both condition aren't verified in the constructor.
        /// </remarks>
        public CrsSparseMatrix(int rowsCount, int columnsCount, int[] rowsBegins, int[] columnsIndexes)
            : this(rowsCount, columnsCount, rowsBegins, columnsIndexes, null)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="CrsSparseMatrix{T}"/> class.
        /// </summary>
        /// <param name="rowsCount">
        ///     The number of rows in the new <see cref="CrsSparseMatrix{T}"/>.
        /// </param>
        /// <param name="columnsCount">
        ///     The number of columns of the new <see cref="CrsSparseMatrix{T}"/>.
        /// </param>
        /// <param name="rowsBegins">
        ///     The collection of indexes of rows begins.
        /// </param>
        /// <param name="columnsIndexes">
        ///     The collection of zero-based indexes of columns of nonzero elements.
        /// </param>
        /// <param name="data">
        ///     The collection of values of nonzero elements.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="rowsBegins"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NegativeOrZeroSizeException">
        ///     <paramref name="rowsCount"/> is negative or equal to zero.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnsCount"/> is negative or equal to zero.
        ///     </para>
        /// </exception>
        /// <exception cref="NoEnoughElementsException">
        ///     <paramref name="rowsBegins"/> has count of elements less than <paramref name="rowsCount"/> + 1.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnsIndexes"/> has count of elements less than
        ///         <paramref name="rowsBegins"/>[<paramref name="rowsCount"/>] - <paramref name="rowsBegins"/>[0].
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="data"/> is not <see langword="null"/> and has count of elements less than
        ///         <paramref name="rowsBegins"/>[<paramref name="rowsCount"/>] - <paramref name="rowsBegins"/>[0].
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The elements of <paramref name="rowsBegins"/> should be nonnegative and
        ///     each element of <paramref name="rowsBegins"/> should be equal to or greater than it's previous element,
        ///     but both condition aren't verified in the constructor.
        /// </remarks>
        public CrsSparseMatrix(int rowsCount, int columnsCount, int[] rowsBegins, int[] columnsIndexes, T[] data)
        {
            #if SAFE

            if (rowsBegins == null)
            {
                throw new ArgumentNullException("rowsBegins");
            }

            if (columnsIndexes == null)
            {
                throw new ArgumentNullException("columnsIndexes");
            }

            if (rowsCount <= 0)
            {
                throw new NegativeOrZeroSizeException("rowsCount", rowsCount);
            }

            if (columnsCount <= 0)
            {
                throw new NegativeOrZeroSizeException("columnsCount", columnsCount);
            }

            if (rowsBegins.Length < rowsCount + 1)
            {
                throw new NoEnoughElementsException("rowsBegins", rowsBegins.Length, rowsCount + 1);
            }

            #endif

            var elementsCount = rowsBegins[rowsCount] - rowsBegins[0];

            #if SAFE

            if (columnsIndexes.Length < elementsCount)
            {
                throw new NoEnoughElementsException("columnsIndexes", columnsIndexes.Length, elementsCount);
            }

            if ((data != null) && (data.Length < elementsCount))
            {
                throw new NoEnoughElementsException("data", data.Length, elementsCount);
            }

            #endif

            this.RowsCount = rowsCount;
            this.ColumnsCount = columnsCount;
            this.RowsBegins = rowsBegins;
            this.ColumnsIndexes = columnsIndexes;
            this.Values = data ?? new T[elementsCount];
        }

        #endregion

        #endregion

        #region Properties

        #region public

        /// <summary>
        ///     Gets the collection of zero-based indexes of columns of nonzero elements.
        /// </summary>
        /// <value>
        ///     The collection of zero-based indexes of columns of nonzero elements.
        /// </value>
        public int[] ColumnsIndexes
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of indexes of rows begins.
        /// </summary>
        /// <value>
        ///     The collection of indexes of rows begins.
        /// </value>
        public int[] RowsBegins
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of values of nonzero elements.
        /// </summary>
        /// <value>
        ///     The collection of values of nonzero elements.
        /// </value>
        public T[] Values
        {
            get;
            private set;
        }

        #endregion

        #endregion

        #region Overridden Properties

        #region public

        /// <summary>
        ///     Gets the registration index of the matrix that uniquely identifies type of matrix.
        /// </summary>
        /// <value>
        ///     The registration index of the matrix that uniquely identifies type of matrix.
        /// </value>
        public override int MatrixRegistrationIndex
        {
            get
            {
                return RegistrationIndex;
            }
        }

        #endregion

        #endregion
    }
}